#!/usr/bin/perl

######################################################################
package Swignition::uF::hEntry;
######################################################################

use CGI::Util;
use Swignition::GenericParser::Utils;
use Swignition::uF;
use XML::LibXML qw(:all);
use strict;

sub parse_all
{
	my $page   = shift;
	my $within = shift || $page->{DOM};
	my @nodes1 = searchClass('hentry', $within);
	my @nodes2 = searchClass('hslice', $within);
	my @nodes = (@nodes1, @nodes2);
	my @rv;
	
	foreach my $e (@nodes)
	{
		my $parent = $e->parentNode;
		my $found_real_feed = 0;
		
		while (($parent->nodeType == XML::LibXML::XML_ELEMENT_NODE)
			&& ($parent != $within)
			&& ($parent != $page->{DOM}->getDocumentElement)
			&& ($parent->parentNode->nodeType == XML::LibXML::XML_ELEMENT_NODE))
		{
			if ($parent->getAttribute('class') =~ /hfeed/)
			{
				$found_real_feed = 1;
				last;
			}
			$parent = $parent->parentNode;
		}

		if (!$found_real_feed || ($found_real_feed && ($parent == $within)))
		{
			my $E = parse($page, $e);
			push @rv, $E;
		}
	}
	
	return @rv;
} #/sub parse_all


sub uri
{
	my $this = shift;
	my $all  = shift;
	my @rv;

	push @rv, Swignition::GenericParser::Utils::BNodeURI($this->{_dom}, 'FeedEntry')
		unless (@rv);
	
	return $rv[0] unless (defined $all);
	while ($all) { shift @rv; $all--; }
	return @rv;
} #/sub uri


sub urix
{
	my $this  = shift;
	my $thing = shift;
	$thing =~ s/^([a-z])/uc($1)/e;
	return Swignition::GenericParser::Utils::BNodeURI($this->{_dom}, 'FeedEntry').'_'.$thing;
}

sub rdf_subject
{
	my $this    = shift;
	my $ns      = 'http://bblfish.net/work/atom-owl/2006-06-06/#';

	# Create RDF subject
	my $subject = Swignition::RDFModel::Subject->new($this->uri);
	$subject->addObject($this);
	$subject->setDom($this->{_dom});
	
	$subject->addVal('http://www.w3.org/1999/02/22-rdf-syntax-ns#type',
		Swignition::RDFModel::Value->new($ns.'Entry', 1));
	
	foreach my $p (qw(self replies enclosure))
	{
		my @links;
		
		if ($p eq 'self') # one simple link
			{ @links = ($this->{'link'}); }
		elsif ($this->{$p}) # embedded objects
		{
			foreach my $o (@{$this->{$p}})
			{
				if (ref $o)
					{ push @links, $o->uri; }
				else
					{ push @links, $o; }
			}
		}

		foreach my $l (@links)
		{
			my $linkSubject = Swignition::RDFModel::Subject->new(
				Swignition::GenericParser::Utils::BNodeURI(undef, 'AtomLink'));
			$linkSubject->addVal('http://www.w3.org/1999/02/22-rdf-syntax-ns#type',
				Swignition::RDFModel::Value->new($ns.'Link', 1));
			$linkSubject->addVal($ns.'subject',
				Swignition::RDFModel::Value->new($subject->uri, 1));
			$linkSubject->addVal($ns.'rel',
				Swignition::RDFModel::Value->new('http://www.iana.org/assignments/relation/'.$p, 1));
			my $linkObject = Swignition::RDFModel::Subject->new(
				Swignition::GenericParser::Utils::BNodeURI(undef, 'AtomLinkObject'));
			$linkObject->addVal('http://www.w3.org/1999/02/22-rdf-syntax-ns#type',
				Swignition::RDFModel::Value->new($ns.'Content', 1));
			$linkObject->addVal($ns.'src',
				Swignition::RDFModel::Value->new($l, 1));
			$linkSubject->addVal($ns.'to',
				Swignition::RDFModel::Value->new($linkObject->uri, 1));
			$subject->addVal($ns.'link',
				Swignition::RDFModel::Value->new($linkSubject->uri, 1));
			$subject->addVal('http://www.iana.org/assignments/relation/'.$p,
				Swignition::RDFModel::Value->new($l, 1));
			
			$this->{'_page'}->mergeSubject($linkSubject);
			$this->{'_page'}->mergeSubject($linkObject);
		}
	}

	my $alternativePredicates = {
		'title'   => 'http://purl.org/dc/terms/title',
		'summary' => 'http://purl.org/dc/terms/abstract'
	};
	foreach my $p (qw(title content summary))
	{
		next unless (length $this->{$p});

		if ($alternativePredicates->{$p})
		{
			$subject->addVal($alternativePredicates->{$p},
				Swignition::RDFModel::Value->new($this->{$p}));
		}
		
		my $contentSubject = Swignition::RDFModel::Subject->new($this->urix($p));
		$contentSubject->addVal('http://www.w3.org/1999/02/22-rdf-syntax-ns#type',
			Swignition::RDFModel::Value->new($ns.'Content', 1));
		$subject->addVal($ns.$p,
			Swignition::RDFModel::Value->new($contentSubject->uri, 1));
			
		my $contentBody = Swignition::RDFModel::Value->new( 
			($p eq 'title' ? $this->{$p} : $this->{'html_'.$p} ));
		$contentBody->setDatatype('http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral')
			unless ($p eq 'title');
		$contentSubject->addVal($ns.'body', $contentBody);
		$contentSubject->addVal($ns.'type',
			Swignition::RDFModel::Value->new($p eq 'title' ? 'text/plain' : 'application/xhtml+xml'));

		$this->{'_page'}->mergeSubject($contentSubject);
	}

	# Patterns for simple data.
	my @_SINGLES  = qw(published);
	my @_SINGLESR = qw(parent);
	my @_MULTIS   = qw(updated);
	my @_MULTISR  = qw(child sibling license in-reply-to);
	my @_OBJECTS  = qw(author category);
	
	# Properties which exist as singles.
	foreach my $property (@_SINGLES)
	{
		next unless (length $this->{$property});
		my $val = Swignition::RDFModel::Value->new($this->{$property});
		$subject->addVal($ns.$property, $val);
	}

	# Resource-type properties which exist as singles.
	foreach my $property (@_SINGLESR)
	{
		my $myns = $ns;
		$myns = 'http://buzzword.org.uk/rdf/icalx#' if ($property eq 'parent');
		next unless (length $this->{$property});
		my $val = Swignition::RDFModel::Value->new($this->{$property}, 1);
		$subject->addVal($myns.$property, $val);
	}
		
	# Properties which exist as multiple values.
	foreach my $property (@_MULTIS)
	{
		foreach my $V (@{ $this->{$property} })
		{
			next unless (length $V);
			my $val = Swignition::RDFModel::Value->new($V);
			$subject->addVal($ns.$property, $val);
		}
	}
	
	# Resource-type properties which exist as multiple values.
	foreach my $property (@_MULTISR)
	{
		foreach my $V (@{ $this->{$property} })
		{
			my $myns = $ns;
			$myns = 'http://buzzword.org.uk/rdf/icalx#' if ($property eq 'child' || $property eq 'sibling');
			$myns = 'http://buzzword.org.uk/rdf/atomix#' if ($property eq 'in-reply-to');
			$myns = 'http://www.w3.org/1999/xhtml/vocab#' if ($property eq 'license');
			next unless (length $V);
			my $val = Swignition::RDFModel::Value->new($V, 1);
			$subject->addVal($myns.$property, $val);
		}
	}

	# Embedded objects which may be represented as resources
	foreach my $property (@_OBJECTS)
	{
		my $myns = $ns;
		$myns = 'http://buzzword.org.uk/rdf/atomix#' if ($property eq 'replies');
		foreach my $V (@{ $this->{$property} })
		{
			my $val;
			if (ref $V)
				{ $val = Swignition::RDFModel::Value->new($V->uri, 1); }
			else
				{ $val = Swignition::RDFModel::Value->new($V); }
			$subject->addVal($myns.$property, $val);
		}
	}
	
	return $subject;
	
} #/sub rdf_subject


sub metadata_ns
{
	my $this = shift;
	return { 
		'awol'  => { nsurl=>'http://bblfish.net/work/atom-owl/2006-06-06/#',title=>'Atom OWL' },
		'awolx' => { nsurl=>'http://buzzword.org.uk/rdf/atomix#',title=>'Atom Threading' }
	};
} #/sub namespace


sub parse
{
	my $page = shift;
	my $rv = { '_dom'=>shift };
	my $pkg = __PACKAGE__;
	if (defined $page->{uF}->{_Shortcuts}->{$pkg}->{ $rv->{_dom}->getAttribute('_xpath') })
		{ return $page->{uF}->{_Shortcuts}->{$pkg}->{ $rv->{_dom}->getAttribute('_xpath') }; }
	else
		{ $page->{uF}->{_Shortcuts}->{$pkg}->{ $rv->{_dom}->getAttribute('_xpath') } = $rv; }
	
	bless $rv;
	
	my $root_node = $rv->{'_dom'}->cloneNode(1);
	
	my $id = $root_node->getAttribute('id');
	$rv->{_id} = $id if (length $id);
	$rv->{_page} = $page;
	
	data_patterns($page, $root_node, 6);

	# Author - too complex for simple_parse().
	my @nested = Swignition::GenericParser::Utils::searchClass('vcard', $root_node);
	foreach my $h (@nested)
	{
		if ($h->getAttribute('class') =~ /\b(author)\b/)
		{
			push @{ $rv->{author} }, Swignition::uF::hCard::parse($page, $h);
		}
	}	
	if (!defined $rv->{author})
	{
		foreach my $h (@nested)
		{
			if ($h->tagName eq 'address')
			{
				push @{ $rv->{author} }, Swignition::uF::hCard::parse($page, $h);
			}
		}
		if (!defined $rv->{author})
		{
			foreach my $address ($root_node->getElementsByTagName('address'))
			{
				push @{ $rv->{author} }, Swignition::uF::hCard::parse_all($page, $address);
			}
		}
	}
	
	simple_parse(
		$rv,
		$root_node,
		[
			['bookmark',        'ru?',  {'use-key'=>'link'}],
			['entry-content',   'H&',   {'use-key'=>'content'}],
			['entry-summary',   'H&',   {'use-key'=>'summary'}],
			['in-reply-to',     'Ru*'],
			['published',       'd?'],
			['replies',         'm*',   {'embedded'=>'hAtom'}],
			['updated',         'd*']
		],
		{
			'rel-tag'       => 'category',
			'rel-enclosure' => 'enclosure',
			'rel-license'   => 'license'
		}
	);
	
	# Entry title - has weird fallback rules.
	my @nodes = Swignition::GenericParser::Utils::searchClass('entry-title', $root_node);
	if (@nodes)
	{
		$rv->{title} = STRINGIFY($nodes[0], 'value');
	}
	else
	{
		foreach my $tag ($root_node->getElementsByTagName('*'))
		{
			if ($tag->tagName =~ /^h[1-9]?$/i)
			{
				$rv->{title} = STRINGIFY($tag, 'value');
				last;
			}
		}
	}
	
	# If this is the only entry on the page, and there are no hfeeds, then
	# take the page's title.
	if (!defined $rv->{title})
	{
		my @feeds = searchClass('hfeed', $page->{DOM});
		if (!@feeds)
		{
			my @entries = searchClass('hentry', $page->{DOM});
			my @slices  = searchClass('hslice', $page->{DOM});
			push @entries, @slices;
			if (!$entries[1])
			{
				my @titles = $page->{DOM}->getElementsByTagName('title');
				$rv->{title} = STRINGIFY($titles[0]);
			}
		}
	}
		
	# Bookmark - fallback rule
	unless (length $rv->{'link'})
	{
		$rv->{'link'} = $rv->uri;
	}
	
	# Handling replies hAtom feed:
	if ($rv->{'replies'})
	{
		foreach my $feed (@{$rv->{'replies'}})
		{
			foreach my $entry (@{$feed->{'entry'}})
			{
				if (! $entry->{'in-reply-to'})
				{
					push @{ $entry->{'in-reply-to'} }, $rv->{'link'};
				}
			}
		}
	}
	
	return $rv;
	
} #/sub parse


1; #/package Swignition::uF::hEntry

